unit PtSettingsControls;

interface
{$IFDEF VCL}
{$ELSE}
{$ENDIF}

uses
{$IFDEF VCL}
  Controls, StdCtrls, ComCtrls, ExtCtrls, Forms,
{$ELSE}
  FMX.Controls, FMX.Types, FMX.StdCtrls, FMX.TreeView,
{$ENDIF}
  SysUtils, Classes, Generics.Collections, TypInfo, Types,
  PtSettingsEditorFactory, PtSettings;

type
{$IFDEF VCL}
  TSettingsPanel = class(TCustomPanel)
{$ELSE}
  TSettingsPanel = class(TControl)
{$ENDIF}
  private
    fSettings: TSettingsGroup;
    fPanCaptions: TPanel;
    fSplitter: TSplitter;
    fPanEditors: TPanel;
    fCaptions: TObjectList<TLabel>;
{$IFDEF VCL}
    fEditors: TObjectList<TWinControl>;
{$ELSE}
    fEditors: TObjectList<TControl>;
{$ENDIF}
    fCaptionOffsetY: integer;
    fCaptionOffsetX: integer;
    fSystemToTitle: TSystemToTitle;
    procedure SetSettings(const Value: TSettingsGroup);
{$IFDEF VCL}
    function  GetBevelSplitter: Boolean;
    procedure SetBevelSplitter(const Value: Boolean);
    function  GetCaptionWidth: integer;
    procedure SetCaptionWidth(const Value: integer);
{$ELSE}
    function  GetCaptionWidth: Single;
    procedure SetCaptionWidth(const Value: Single);
{$ENDIF}
  protected
    procedure GetPropNames(const Names: TStringList);
    function  GetPropType(const Name: string): TTypeKind;
    function  GetPropValue(const Name: string): Variant;
    procedure SetPropValue(const Name: string; Value: Variant);
    procedure PopulateControl;
    procedure PopulateSettings;
    function  DoSystemToTitle(const Str: string): string;
    procedure ExitEditors(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    property  Settings  : TSettingsGroup  read fSettings  write SetSettings;
  published
{$IFDEF VCL}
    property  CaptionWidth: integer read GetCaptionWidth write SetCaptionWidth;
{$ELSE}
    property  CaptionWidth: Single read GetCaptionWidth write SetCaptionWidth;
{$ENDIF}
    property  CaptionOffsetX : integer  read fCaptionOffsetX write fCaptionOffsetX;
    property  CaptionOffsetY : integer  read fCaptionOffsetY write fCaptionOffsetY;
    property  OnSystemToTitle : TSystemToTitle  read fSystemToTitle write fSystemToTitle;
    //  Inherited
    property  Align;
{$IFDEF VCL}
    property  BevelInner;
    property  BevelOuter;
    property  BevelSplitter: Boolean  read GetBevelSplitter write SetBevelSplitter default True;
{$ENDIF}
    property  Visible;
  end;

  TSettingsTree = class(TCustomTreeView)
  private //  Fields and properties
    fSettings: TSettingsGroup;
    fSystemToTitle: TSystemToTitle;
    fSettingsPanel: TSettingsPanel;
    function  GetSelected: TSettingsGroup;
    procedure SetSettings(const Value: TSettingsGroup);
//    procedure Change(Node: TTreeViewItem);
  protected
    procedure Populate;
    function  DoSystemToTitle(const Str: string): string;
{$IFDEF VCL}
    procedure Change(Node: TTreeNode); override;
{$ELSE}
    procedure SetSelected(const Value: TTreeViewItem); override;
{$ENDIF}
  public
    constructor Create(AOwner: TComponent); override;
    property  Settings  : TSettingsGroup  read fSettings    write SetSettings;
    property  Selected  : TSettingsGroup  read GetSelected;
  published
    property  SettingsPanel: TSettingsPanel read fSettingsPanel write fSettingsPanel;
    property  OnSystemToTitle : TSystemToTitle  read fSystemToTitle write fSystemToTitle;
    //  Inherited
    property  Align;
{$IFDEF VCL}
    property  BorderStyle;
    property  HideSelection default False;
    property  Images;
    property  Indent;
{$ENDIF}
//    property  Items;
    property  Visible;
  end;

procedure ListEnumValues(ATypeInfo: PTypeInfo; Strings: TStrings);

procedure Register;

implementation

const
  SEUnrecognisedType  = 'Unrecognised type';

procedure ListEnumValues(ATypeInfo: PTypeInfo; Strings: TStrings);
var
  TypeData: PTypeData;
  i: integer;
begin
  Strings.Clear;
  TypeData := GetTypeData(ATypeInfo);
  for i := TypeData.MinValue to TypeData.MaxValue do
    Strings.AddObject(GetEnumName(ATypeInfo, i), TObject(i));
end;

procedure Register;
begin
  RegisterComponents('PtLibrary', [TSettingsTree]);
  RegisterComponents('PtLibrary', [TSettingsPanel]);
end;

{ TSettingsFrame }

constructor TSettingsPanel.Create(AOwner: TComponent);
begin
  inherited;
  fCaptionOffsetX := 8;
  fCaptionOffsetY := 3;
  //  Containers
  fCaptions := TObjectList<TLabel>.Create;
{$IFDEF VCL}
  fEditors  := TObjectList<TWinControl>.Create;
{$ELSE}
  fEditors  := TObjectList<TControl>.Create;
{$ENDIF}
  //  Panels
  fPanCaptions  := TPanel.Create(Self);
  fPanCaptions.Parent := Self;
{$IFDEF VCL}
  fPanCaptions.Align  := alLeft;
  fPanCaptions.Width  := Self.Width div 2;
  fPanCaptions.BevelInner := bvNone;
  fPanCaptions.BevelOuter := bvNone;
{$ELSE}
  fPanCaptions.Align  := TAlignLayout.Left;
  fPanCaptions.Width  := Self.Width / 2;
//  fPanCaptions.BevelInner := bvNone;
//  fPanCaptions.BevelOuter := bvNone;
{$ENDIF}
  fSplitter         := TSplitter.Create(Self);
  fSplitter.Parent  := Self;
{$IFDEF VCL}
  fSplitter.Align   := alLeft;
  fSplitter.Left    := fPanCaptions.Left + fPanCaptions.Width + 5;
  fSplitter.Beveled := True;
{$ELSE}
  fSplitter.Align   := TAlignLayout.Left;
  fSplitter.Position.X  := fPanCaptions.Position.X + fPanCaptions.Width + 5;
//  fSplitter.Beveled := True;
{$ENDIF}
  fPanEditors         := TPanel.Create(Self);
  fPanEditors.Parent  := Self;
{$IFDEF VCL}
  fPanEditors.Align   := alClient;
  fPanEditors.BevelInner  := bvNone;
  fPanEditors.BevelOuter  := bvNone;
{$ELSE}
  fPanEditors.Align   := TAlignLayout.Client;
//  fPanEditors.BevelInner  := bvNone;
//  fPanEditors.BevelOuter  := bvNone;
{$ENDIF}
  fPanEditors.OnExit  := ExitEditors;
end;

function TSettingsPanel.DoSystemToTitle(const Str: string): string;
begin
  if  Assigned(fSystemToTitle)  then
    Result  := fSystemToTitle(Str)
  else
    Result  := Str
end;

procedure TSettingsPanel.ExitEditors(Sender: TObject);
begin
  PopulateSettings;
end;

{$IFDEF VCL}
function TSettingsPanel.GetBevelSplitter: Boolean;
begin
  Result  := fSplitter.Beveled
end;
{$ENDIF}

{$IFDEF VCL}
function TSettingsPanel.GetCaptionWidth: integer;
{$ELSE}
function TSettingsPanel.GetCaptionWidth: Single;
{$ENDIF}
begin
  Result  := fPanCaptions.Width
end;

procedure TSettingsPanel.GetPropNames(const Names: TStringList);
var
  nr, i : integer;
  PropList  : PPropList;
begin
  Names.Clear;
  nr  := GetPropList( Settings, PropList );
  for i := 0 to nr-1  do
    Names.Add( PropList[i].Name )
end;

function TSettingsPanel.GetPropType(const Name: string): TTypeKind;
begin
  Result  := PropType( Settings, Name )
end;

function TSettingsPanel.GetPropValue(const Name: string): Variant;
begin
  case  GetPropType( Name )  of
    tkInteger, tkChar, tkWChar, tkClass:
      Result  := GetOrdProp( Settings, Name );

    tkEnumeration:
      Result  := GetEnumProp( Settings, Name );

    tkSet:
      Result  := GetSetProp( Settings, Name );

    tkFloat:
      Result  := GetFloatProp( Settings, Name );

    tkString, tkLString:
      Result  := GetStrProp( Settings, Name );

    tkWString, tkUString:
      Result  := GetStrProp( Settings, Name );

    tkInt64:
      Result  := GetInt64Prop( Settings, Name );

  else
    raise EPtSettings.Create(SEUnrecognisedType);
  end;
end;

procedure TSettingsPanel.PopulateControl;
var
  i: integer;
  Names: TStringList;
  Name: string;
  Caption: TLabel;
{$IFDEF VCL}
  Editor: TWinControl;
{$ELSE}
  Editor: TControl;
{$ENDIF}
  TypeKind: TTypeKind;
begin
  fCaptions.Clear;
  fEditors.Clear;
  if  not Assigned(fSettings) then
    exit;
  Names := TStringList.Create;
  try
    GetPropNames( Names );
    for i := 0 to Names.Count-1 do
      begin
        Name      := Names[i];
        TypeKind  := GetPropType(Name);
        //  Create and place editor
        Editor    := EditorFactory.CreateFrame(Self, fPanEditors, GetPropInfo(Settings, Name).PropType^, TypeKind);
        if  not Assigned(Editor)  then
          continue;
{$IFDEF VCL}
        Editor.AlignWithMargins := True;
{$ENDIF}
        Editor.Margins.Left   := 4;
        Editor.Margins.Top    := 4;
        Editor.Margins.Right  := 8;
        Editor.Margins.Bottom := 4;
{$IFDEF VCL}
        Editor.Top            := 10000;
        Editor.Align          := alTop;
{$ELSE}
        Editor.Position.Y     := 10000;
        Editor.Align          := TAlignLayout.Top;
{$ENDIF}
        Editor.Name           := Name;
        fEditors.Add(Editor);
        //  Create and place caption
        Caption := TLabel.Create(Self);
        Caption.Parent  := fPanCaptions;
{$IFDEF VCL}
        Editor.AlignWithMargins := True;
{$ENDIF}
        Editor.Margins.Left   := 8;
        Editor.Margins.Top    := 4;
        Editor.Margins.Right  := 4;
        Editor.Margins.Bottom := 4;
{$IFDEF VCL}
        Caption.Left        := fCaptionOffsetX;
        Caption.Top         := Editor.Top    + fCaptionOffsetY;
{$ELSE}
        Caption.Position.X  := fCaptionOffsetX;
        Caption.Position.Y  := Editor.Position.Y + fCaptionOffsetY;
{$ENDIF}
        Caption.Height      := Editor.Height;
        Caption.Name        := 'lab' + Name;
{$IFDEF VCL}
        Caption.Caption     := DoSystemToTitle(Name);
{$ELSE}
        Caption.Text        := DoSystemToTitle(Name);
{$ENDIF}
        fCaptions.Add(Caption);
        //  Populate Value
        (Editor as ISettingsTypeEditor).SetValue(GetPropValue(Name));
      end;
  finally
    Names.Free;
  end;
end;

procedure TSettingsPanel.PopulateSettings;
var
  i: integer;
{$IFDEF VCL}
  Editor: TWinControl;
{$ELSE}
  Editor: TControl;
{$ENDIF}
begin
  if  not Assigned(fSettings) then
    exit;
  for i := 0 to fEditors.Count - 1  do
    begin
      Editor  := fEditors[i];
      SetPropValue(Editor.Name, (Editor as ISettingsTypeEditor).GetValue);
    end;
end;

{$IFDEF VCL}
procedure TSettingsPanel.SetBevelSplitter(const Value: Boolean);
begin
  fSplitter.Beveled := Value
end;
{$ENDIF}

{$IFDEF VCL}
procedure TSettingsPanel.SetCaptionWidth(const Value: integer);
{$ELSE}
procedure TSettingsPanel.SetCaptionWidth(const Value: Single);
{$ENDIF}
begin
  fPanCaptions.Width := Value
end;

procedure TSettingsPanel.SetPropValue(const Name: string; Value: Variant);
begin
  case  GetPropType( Name )  of
      tkInteger, tkChar, tkWChar, tkClass:
        SetOrdProp( Settings, Name, Value );

      tkEnumeration:
        SetEnumProp( Settings, Name, Value );

      tkSet:
        SetSetProp( Settings, Name, Value );

      tkFloat:
        SetFloatProp( Settings, Name, Value );

      tkString, tkLString:
        SetStrProp( Settings, Name, Value );

      tkWString, tkUString:
        SetStrProp( Settings, Name, Value );

      tkInt64:
		    SetInt64Prop( Settings, Name, Value );

    else
      raise EPtSettings.Create(SEUnrecognisedType);
  end;
end;

procedure TSettingsPanel.SetSettings(const Value: TSettingsGroup);
begin
  PopulateSettings;
  fSettings := Value;
  PopulateControl;
end;

{ TSettingsTree }

{$IFDEF VCL}
procedure TSettingsTree.Change(Node: TTreeNode);
begin
  inherited;
  if  Assigned(fSettingsPanel)  then
    fSettingsPanel.Settings := Selected;
end;
{$ENDIF}

constructor TSettingsTree.Create(AOwner: TComponent);
begin
  inherited;
{$IFDEF VCL}
  HideSelection := False;
  ReadOnly      := True;
{$ELSE}
//  HideSelection := False;
//  ReadOnly      := True;
{$ENDIF}
end;

function TSettingsTree.DoSystemToTitle(const Str: string): string;
begin
  if  Assigned(fSystemToTitle)  then
    Result  := fSystemToTitle(Str)
  else
    Result  := Str
end;

function TSettingsTree.GetSelected: TSettingsGroup;
var
{$IFDEF VCL}
  Node: TTreeNode;
{$ELSE}
  Node: TTreeViewItem;
{$ENDIF}
begin
  Node  := inherited Selected;
  if  Node = nil then
    Result  := nil
  else
{$IFDEF VCL}
    Result  := Node.Data
{$ELSE}
    Result  := Node.Data.AsObject as TSettingsGroup
{$ENDIF}
end;

procedure TSettingsTree.Populate;

{$IFDEF VCL}
  procedure PopulateNode(const ParentNode: TTreeNode; const Group: TSettingsGroup);
{$ELSE}
  procedure PopulateNode(const ParentNode: TTreeViewItem; const Group: TSettingsGroup);
{$ENDIF}
  var
    i : integer;
{$IFDEF VCL}
    Node  : TTreeNode;
{$ELSE}
    Node  : TTreeViewItem;
{$ENDIF}
  begin
{$IFDEF VCL}
    Node  := Items.AddChildObject(ParentNode, DoSystemToTitle(Group.GroupName), Group);
{$ELSE}
    Node  := TTreeViewItem.Create(Self);
    Node.Parent := ParentNode;
    Node.Text := DoSystemToTitle(Group.GroupName);
    Node.Data.From(Group);
{$ENDIF}
    for i := 0 to Group.SubGroupCount-1 do
      PopulateNode(Node, Group.SubGroups[i]);
  end;

begin
{$IFDEF VCL}
  Items.Clear;
{$ELSE}
  Self.Clear;
{$ENDIF}
  if  Assigned(fSettings) then
    PopulateNode( nil, Settings );
{$IFDEF VCL}
  if  Assigned(TopItem) then
    TopItem.Expand(True);
{$ELSE}
  if  Count > 0 then
    Items[0].Expand;
{$ENDIF}
end;

{$IFNDEF VCL}
procedure TSettingsTree.SetSelected(const Value: TTreeViewItem);
begin
  inherited;
  if  Assigned(fSettingsPanel)  then
    fSettingsPanel.Settings := Selected;
end;
{$ENDIF}

procedure TSettingsTree.SetSettings(const Value: TSettingsGroup);
begin
  fSettings := Value;
  Populate;
{$IFDEF VCL}
  if  Items.Count > 0 then
    inherited Selected  := TopItem
{$ELSE}
  if  Count > 0 then
    inherited Selected  := Items[0]
{$ENDIF}
  else
    inherited Selected  := nil;
end;

end.
